package org.restql.webapp;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.restql.db.operation.DatabaseDelete;
import org.restql.db.operation.DatabaseDtd;
import org.restql.db.operation.DatabaseGet;
import org.restql.db.operation.DatabaseInsert;
import org.restql.db.operation.DatabaseSelect;
import org.restql.db.operation.DatabaseUpdate;
import org.restql.db.resource.DatabaseResource;
import org.restql.db.resource.ModifiableDatabaseResource;
import org.restql.db.resource.ModifiableDatabaseResourceImpl;
import org.restql.db.resource.RequestResource;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;

public class RestQLService {
	private static final long serialVersionUID = 1L;
	private static Log logger = LogFactory.getLog(RestQLService.class);
	private Preferences userPrefs;
	private RestQLResourceContainer resourceContainer = null;
	private String configFilePath = null;
	private boolean inited = false;

	// parseRequest instance variables
	String requestedPath;
	String []pathElements;
	String dbName;
	String tableName;
	String show;
	ReturnType retType = ReturnType.xml;
	Map params;
	/*
	private String jdbcUrl;
	private String jdbcDriver;
	private String username;
	private String password;
	*/
	//private String jdbcUrl = "jdbc:mysql://localhost:3306/entertainment";
	//private String jdbcDriver = "com.mysql.jdbc.Driver";
	private RestQLServlet servlet;
	
	public RestQLService(RestQLServlet rqlServlet) {
		this.servlet = rqlServlet;
		this.logger = LogFactory.getLog(RestQLService.class);
		this.userPrefs = rqlServlet.userPrefs;
		this.resourceContainer = rqlServlet.resourceContainer ;
		this.configFilePath = rqlServlet.configFilePath;
	}
	
	

	public boolean parseRequest (HttpServletRequest request, HttpServletResponse response) {
		if (!inited) { 
			//request.getMethod()
			requestedPath = request.getPathInfo();
			if (requestedPath.startsWith("/dtd/")) {
				retType = ReturnType.dtd;
				requestedPath = requestedPath.substring("/dtd/".length() - 1);
			} else if(requestedPath.startsWith("/xml/")) {
				retType = ReturnType.xml;
				requestedPath = requestedPath.substring("/xml/".length() - 1);
			} else if(requestedPath.startsWith("/xsd/")) {
				retType = ReturnType.xsd;
				requestedPath = requestedPath.substring("/xsd/".length() - 1);
			} else if(requestedPath.startsWith("/json/")) {
				retType = ReturnType.json;
				requestedPath = requestedPath.substring("/json/".length() - 1);
			}
	
			pathElements = requestedPath.split("/");
			
			if (! validatePath(pathElements)) {
				return false;
			} else {
	
				params = new LinkedHashMap();
				String keyField = null;
				String keyValue = null;
				dbName = pathElements[1];
				tableName = pathElements[2];
				if (pathElements.length == 4) {
					keyField = "id";
					keyValue = pathElements[3];
					show = keyValue;
					params.put(keyField, new String[] { (String) keyValue} );
				} else if (pathElements.length == 5) {
					keyField = pathElements[3];
					keyValue = pathElements[4];
					if (keyField.equals("show")) {
						show = keyValue;
						keyField = "id";
					}				
					params.put(keyField, new String[] { (String) keyValue} );
				}
	
				if (tableName.endsWith(".dtd")) {
					tableName = tableName.substring(0, tableName.length() - ".dtd".length());
					retType = ReturnType.dtd;
				} else if (tableName.endsWith(".json")) {
					tableName = tableName.substring(0, tableName.length() - ".json".length());
					retType = ReturnType.json;
				} else if (tableName.endsWith(".xml")) {
					tableName = tableName.substring(0, tableName.length() - ".xml".length());
					retType = ReturnType.xml;
				} else if (tableName.endsWith(".xsd")) {
					tableName = tableName.substring(0, tableName.length() - ".xsd".length());
					retType = ReturnType.xsd;
				}
				return true;
			}
		} else {
			return true;
		}
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

		//// Get user to configure war to start things out -- disable for now
		//if (false && userPrefs.get("configLocation", null) == null) {
		//	//RequestDispatcher view = request.getRequestDispatcher("/config.jsp");
		//	logger.info("Forwarding to config page");
		//	//view.forward(request, response);
		//	response.sendRedirect(request.getContextPath() + "/config.jsp");
		//	return;
		//}
		//
		//// Just return an info page for a request to root
		//if (requestedPath.equals("/")) {
		//	RequestDispatcher view = request.getRequestDispatcher("/intro.jsp");
		//	view.forward(request, response);
		//	return;
		//}
		if (!parseRequest (request, response)) {
			generateErrorResponse(response, "Bad path requested: " + requestedPath);
		} else {

			if (!inited) {
				for (Object k : request.getParameterMap().keySet()) {
					params.put(k, request.getParameterMap().get(k));
				}
			}
			
			try {
				DatabaseResource databaseResource =
					resourceContainer.selectMatchingResource(dbName, tableName);

				AccessControl accessControl = new AccessControl(request, dbName, tableName, request.getMethod(), databaseResource.getUser(), databaseResource.getPassword());
				if (! accessControl.operationAuthorized()) {
					generateErrorResponse(response, "Operation not authorized for this table.");
					return;
				}

				if (databaseResource.allowSelect()) {
					DatabaseGet get = null;
					if (retType == ReturnType.xml) {
						get = new DatabaseSelect(databaseResource, new RequestResource(dbName, tableName, accessControl.getUser(),
								accessControl.getPassword(), params, databaseResource.getJdbcUrl(), databaseResource.getJdbcDriver()));
					} else if (retType == ReturnType.json) {
						get = new DatabaseSelect(databaseResource, new RequestResource(dbName, tableName, accessControl.getUser(),
								accessControl.getPassword(), params, databaseResource.getJdbcUrl(), databaseResource.getJdbcDriver()));
					} else if (retType == ReturnType.dtd) {
						get = new DatabaseDtd(databaseResource, new RequestResource(dbName, tableName, accessControl.getUser(),
								accessControl.getPassword(), params, databaseResource.getJdbcUrl(), databaseResource.getJdbcDriver()));
					}
					get.executeSql(response.getWriter(), retType, show);
					response.setContentType(retType.contentType());
				} else {
					generateErrorResponse(response, "Select not allowed for this resource.");
				}
			} catch (RestQLException e) {
				if (e.getAccessDenied()) {
					generateAccessErrorResponse(response, dbName);
				} else {
					generateErrorResponse(response, e.toString());
				}
			}

		}
	}

	public void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//String requestedPath = request.getPathInfo();
		//String []pathElements = requestedPath.split("/");

		if (! parseRequest(request, response)) { //validatePath(pathElements)
			generateErrorResponse(response, "Bad path requested: " + requestedPath);
		} else {

			if (!inited) {
				for (Object k : request.getParameterMap().keySet()) {
					params.put(k, request.getParameterMap().get(k));
				}
			}

			try {
				DatabaseResource databaseResource =
					resourceContainer.selectMatchingResource(dbName, tableName);

				AccessControl accessControl = new AccessControl(request, dbName, tableName, request.getMethod(), databaseResource.getUser(), databaseResource.getPassword());
				if (! accessControl.operationAuthorized()) {
					generateErrorResponse(response, "Operation not authorized for this table.");
					return;
				}

				if (databaseResource.allowInsert() && databaseResource instanceof ModifiableDatabaseResource) {
					DatabaseInsert insert = new DatabaseInsert(databaseResource, new RequestResource(dbName, tableName, accessControl.getUser(),
							accessControl.getPassword(), request.getParameterMap(), databaseResource.getJdbcUrl(), databaseResource.getJdbcDriver()));
					insert.executeSql(request.getInputStream(), retType);
					response.setContentType(retType.contentType());
					PrintWriter out = response.getWriter();
					if (retType == ReturnType.xml) {
						out.println("<?xml version='1.0' ?><insert><message>OK</message></insert>");
					} else if (retType == ReturnType.json) {
						out.println("{message=\"OK\"}");
					} else {
						new RuntimeException ("Unsupported response type");
					}
				} else {
					generateErrorResponse(response, "Insert not supported for this resource.");
				}
			} catch (RestQLException e) {
				if (e.getAccessDenied()) {
					generateAccessErrorResponse(response, dbName);
				} else {
					generateErrorResponse(response, e.toString());
				}
			}
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

		if (! parseRequest(request, response)) { //validatePath(pathElements)
			generateErrorResponse(response, "Bad path requested: " + requestedPath);
		} else {

			if (!inited) {
				for (Object k : request.getParameterMap().keySet()) {
					params.put(k, request.getParameterMap().get(k));
				}
			}
			if ("put".equals(params.get("_method"))) {
				params.remove("_method");
				doPut(request, response);
			} else if ("get".equals(params.get("_method"))) {
				params.remove("_method");
				doGet(request, response);
			} else if ("put".equals(params.get("_method"))) {
				params.remove("_method");
				doGet(request, response);
			} else if ("delete".equals(params.get("_method"))) {
				params.remove("_method");
				doGet(request, response);
			} else {

				if (requestedPath.equals("/postConfig")) {
					ConfigProcessor.processPostedConfig(request, response);
					return;
				}
				
				//String dbName = pathElements[1];
				//String tableName = pathElements[2];
	
				try {
					DatabaseResource databaseResource =
						resourceContainer.selectMatchingResource(dbName, tableName);
	
					AccessControl accessControl = new AccessControl(request, dbName, tableName, request.getMethod(), databaseResource.getUser(), databaseResource.getPassword());
					if (! accessControl.operationAuthorized()) {
						generateErrorResponse(response, "Operation not authorized for this table.");
						return;
					}
	
					if (databaseResource.allowUpdate() && databaseResource instanceof ModifiableDatabaseResource) {
						DatabaseUpdate update = new DatabaseUpdate(databaseResource, new RequestResource(dbName, tableName, accessControl.getUser(),
								accessControl.getPassword(), request.getParameterMap(), databaseResource.getJdbcUrl(), databaseResource.getJdbcDriver()));
	
						update.executeSql(request.getInputStream(), retType);
						response.setContentType(retType.contentType());
						PrintWriter out = response.getWriter();
						if (retType == ReturnType.xml) {
							out.println("<?xml version='1.0' ?><update><message>OK</update></insert>");
						} else if (retType == ReturnType.json) {
							out.println("{message=\"OK\"}");
						} else {
							new RuntimeException ("Unsupported response type");
						}
					} else {
						generateErrorResponse(response, "Updates not allowed for this resource.");
					}
				} catch (RestQLException e) {
					if (e.getAccessDenied()) {
						generateAccessErrorResponse(response, dbName);
					} else {
						generateErrorResponse(response, e.toString());
					}
				}
			}
		}
	}

	public void doDelete(HttpServletRequest request, HttpServletResponse response) throws IOException {

		if (! parseRequest(request, response)) { //validatePath(pathElements)
			generateErrorResponse(response, "Bad path requested: " + requestedPath);
		} else {

			try {
				DatabaseResource databaseResource =
					resourceContainer.selectMatchingResource(dbName, tableName);

				if (!inited) {
					for (Object k : request.getParameterMap().keySet()) {
						params.put(k, request.getParameterMap().get(k));
					}
				}

				AccessControl accessControl = new AccessControl(request, dbName, tableName, request.getMethod(), databaseResource.getUser(), databaseResource.getPassword());
				if (! accessControl.operationAuthorized()) {
					generateErrorResponse(response, "Operation not authorized for this table.");
					return;
				}

				if (databaseResource.allowDelete() && databaseResource instanceof ModifiableDatabaseResource) {
					DatabaseDelete delete = new DatabaseDelete(databaseResource, new RequestResource(dbName, tableName, accessControl.getUser(),
							accessControl.getPassword(), request.getParameterMap(), databaseResource.getJdbcUrl(), databaseResource.getJdbcDriver()));
					delete.executeSql(request.getInputStream());
					response.setContentType("text/xml");
					PrintWriter out = response.getWriter();
					out.println("<?xml version='1.0' ?><update><message>OK</update></insert>");
				} else {
					generateErrorResponse(response, "Delete not allowed for this resource.");
				}
			} catch (RestQLException e) {
				if (e.getAccessDenied()) {
					generateAccessErrorResponse(response, dbName);
				} else {
					generateErrorResponse(response, e.toString());
				}
			}
		}
	}

	private void generateAccessErrorResponse(HttpServletResponse response, String dbName) throws IOException {
		if (retType == ReturnType.xml || retType == ReturnType.dtd || retType == ReturnType.xsd) {
			response.setContentType("text/xml");
			PrintWriter out = response.getWriter();
			out.println("<?xml version='1.0' ?><error><message>Authentication required for: " + dbName + "</message></error>");
			response.setHeader("WWW-Authenticate", "Basic realm=\"restql:" + dbName + "\"");
			response.setStatus(401);
		} else if (retType == ReturnType.json) {
			response.setContentType(retType.contentType());
			PrintWriter out = response.getWriter();
			out.println("{error={message=\"Authentication required for: " + dbName + "\"}}");
			response.setHeader("WWW-Authenticate", "Basic realm=\"restql:" + dbName + "\"");
			response.setStatus(401);
		}
	}

	protected boolean validatePath(String []splitPath) {
		if (splitPath.length == 3 || splitPath.length == 4 || splitPath.length == 5) return true;
		else return false;
	}

	protected void generateErrorResponse(HttpServletResponse response, String message) throws IOException {
		if (retType == ReturnType.xml || retType == ReturnType.dtd || retType == ReturnType.xsd) {
			response.setContentType(retType.contentType());
			PrintWriter out = response.getWriter();
			out.println("<?xml version='1.0' ?><error><message>" + message + "</message></error>");
			response.setStatus(400);
		} else if (retType == ReturnType.json) {
			response.setContentType(retType.contentType());
			PrintWriter out = response.getWriter();
			out.println("{error={message=\"" + message + "}}");
			response.setStatus(400);
		}
	}
}
